﻿using Abp.Application.Services;
using Abp.Application.Services.Dto;
using Abp.AutoMapper;
using Abp.Domain.Entities;
using Abp.Domain.Entities.Auditing;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.EntityFrameworkCore.Repositories;
using Abp.Events.Bus;
using Abp.Events.Bus.Entities;
using Abp.Linq.Extensions;
using Abp.Timing;
using Abp.UI;
using EPPlus.Core.Extensions;
using EPPlus.Core.Extensions.Style;
using Hicap.AuthorizationServer.CommonDto;
using Hicap.AuthorizationServer.Core.Caches.Base.Interface;
using Hicap.AuthorizationServer.Core.Common;
using Hicap.AuthorizationServer.Helper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading.Tasks;

namespace Hicap.AuthorizationServer
{
    /// <summary>
    /// 所有一般API通用方法，包含增删改查、导入导出
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TAppService"></typeparam>
    /// <typeparam name="TEditDto"></typeparam>
    /// <typeparam name="TListDto"></typeparam>
    /// <typeparam name="TCacheItem"></typeparam>
    public abstract partial class AuthorizationServerAppServiceBase<TEntity, TAppService, TEditDto, TListDto, TCacheItem>
        : AuthorizationServerAppServiceCore<TEntity, TAppService>, IAuthorizationServerAppServiceBase<TEditDto, TListDto, TEntity>
        where TEntity : FullAuditedEntity<int>, IEntity
        where TAppService : IApplicationService
        where TEditDto : class, IEntityDto<int?>, new()
        where TListDto : class, IEntityDto
        where TCacheItem : class, ICacheItemBase
    {
        #region Member

        private IEventBus EventBus { get; set; }
        private readonly IRepository<TEntity> _repository;
        private readonly ICacheBase<TCacheItem> _cache;
        protected List<Expression<Func<TEntity, dynamic>>> ForeignKeys { get; set; } = new List<Expression<Func<TEntity, dynamic>>>();
        protected Expression<Func<TEntity, bool>> QueryCondition { get; set; } = null;
        protected List<Action<TEditDto, TEntity>> CreateActions { get; set; } = new List<Action<TEditDto, TEntity>>();
        protected List<Action<TEditDto, TEntity>> UpdateActions { get; set; } = new List<Action<TEditDto, TEntity>>();
        protected List<Action<TEntity>> CreateCheckActions { get; set; } = new List<Action<TEntity>>();
        protected List<Action<TEntity>> UpdateCheckActions { get; set; } = new List<Action<TEntity>>();
        protected Func<WorksheetWrapper<TEditDto>, ExcelPackage> ExtendWorksheetWrapper { get; set; }
        private readonly IUnitOfWorkManager _unitOfWorkManager;

        #endregion Member

        #region Constructor

        protected AuthorizationServerAppServiceBase(
            IRepository<TEntity> repository,
            IUnitOfWorkManager unitOfWorkManager, ICacheBase<TCacheItem> cache)
        {
            _unitOfWorkManager = unitOfWorkManager;
            _cache = cache;
            EventBus = NullEventBus.Instance;
            _repository = repository;

            CreateCheckActions.Add(CheckUnique);

            ExtendWorksheetWrapper = null;
        }

        #endregion Constructor

        #region WebApi Base

        /// <summary>
        /// 获取实体的分页列表信息
        /// </summary>
        /// <param name="queryInput"></param>
        /// <returns></returns>
        public virtual async Task<PagedResultDto<TListDto>> PagedDatas(QueryInput queryInput)
        {
            IQueryable<TEntity> query = GetQuery(queryInput);

            int count = await query.CountAsync<TEntity>();

            List<TEntity> result = await query
                .OrderBy(queryInput.Sorting)
                .PageBy(queryInput)
                .ToListAsync();

            List<TListDto> resultListDto = result.MapTo<List<TListDto>>();

            return new PagedResultDto<TListDto>(
                count,
                resultListDto
            );
        }

        /// <summary>
        /// 获取单条数据
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public virtual async Task<TListDto> GetById(int input)
        {
            TEntity dbEntity = await GetQuery().FirstOrDefaultAsync(x => x.Id == input);
            TListDto entity = dbEntity.MapTo<TListDto>();
            return entity;
        }

        /// <summary>
        /// 添加或者修改实体的公共方法
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public virtual async Task<TListDto> Upsert(CreateOrUpdateInput<TEditDto> input)
        {
            if (input.Entity.Id.HasValue)
            {
                return await Update(input.Entity);
            }
            else
            {
                return await Create(input.Entity);
            }
        }

        /// <summary>
        /// 添加或者修改实体的Batch公共方法
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public virtual async Task<List<TListDto>> BatchUpsert(List<CreateOrUpdateInput<TEditDto>> inputs)
        {
            List<TListDto> result = new List<TListDto>();
            foreach (CreateOrUpdateInput<TEditDto> x in inputs)
            {
                result.Add(await Upsert(x));
            }

            return result;
        }

        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="input"></param>
        /// <param name="actions"></param>
        /// <returns></returns>
        public virtual async Task Delete(int input)
        {
            //删除前的逻辑判断，是否允许删除
            await _repository.DeleteAsync(input);
            TEntity entity = (await _cache.GetAsync(input)).MapTo<TEntity>();
            await TriggerChanged(entity);
        }

        /// <summary>
        /// 批量删除的方法
        /// </summary>

        public virtual Task BatchDelete(List<int> input)
        {
            return Task.Run(() =>
            {
                //                PermissionCheck();
                //批量删除前的逻辑判断，是否允许删除
                input.ForEach(async x => await Delete(x));
            });
        }

        /// <summary>
        /// 导出
        /// </summary>
        /// <param name="queryInput"></param>
        /// <returns></returns>
        public virtual async Task<FileContentResult> Export(QueryInput queryInput = null)
        {
            if (queryInput == null) queryInput = new QueryInput { MaxResultCount = int.MaxValue };
            else queryInput.MaxResultCount = int.MaxValue;

            WorksheetWrapper<TEditDto> worksheetWrapper = await GetExcelInitData(queryInput);

            ExcelPackage excelPackage = await GetExcelData(worksheetWrapper);
            FileContentResult result = new FileContentResult(excelPackage.GetAsByteArray(),
                MediaTypeHeaderValue.Parse("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet").MediaType)
            {
                FileDownloadName = string.Join("-", AbpSession.UserId, typeof(TEntity).Name,
                                       Clock.Now.Date.ToString("yyyyMMddHHmmss")) + ".xlsx"
            };

            return result;
        }

        /// <summary>
        /// [upload]导入Excel
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public virtual Task Import(IFormFile file)
        {
            return Task.Run(async () =>
            {
                ExcelPackage excelPackage = new ExcelPackage();
                //Open File
                using (System.IO.Stream stream = file?.OpenReadStream())
                {
                    excelPackage.Load(stream);
                }

                //Get Sheet
                string sheetName = $" {L(typeof(TEntity).Name).TrimStart('[').TrimEnd(']')} ";
                ExcelWorksheet worksheet = excelPackage.GetWorksheet(sheetName);
                if (worksheet == null) worksheet = excelPackage.GetWorksheet(sheetName.Trim());

                //Column Rename
                foreach (OfficeOpenXml.Table.ExcelTableColumn excelTableColumn in worksheet.AsExcelTable().Columns)
                {
                    PropertyInfo pi = typeof(TEditDto).GetProperties().FirstOrDefault(x => L(x.Name).Equals(excelTableColumn.Name));
                    if (pi != null) excelTableColumn.Name = pi.Name;
                }

                //Covert Entities
                List<TEditDto> editDtoList = worksheet.ToList<TEditDto>(cfg => cfg.SkipCastingErrors());

                //Upsert
                List<CreateOrUpdateInput<TEditDto>> createOrUpdateList = new List<CreateOrUpdateInput<TEditDto>>();
                editDtoList.ForEach(x => createOrUpdateList.Add(new CreateOrUpdateInput<TEditDto>() { Entity = x }));
                await BatchUpsert(createOrUpdateList);
            });
        }

        #endregion WebApi Base

        #region Protected Methods

        /// <summary>
        /// 查询条件组装并输出IQeryable
        /// </summary>
        /// <param name="queryInput"></param>
        /// <returns></returns>
        protected virtual IQueryable<TEntity> GetQuery(QueryInput queryInput = null)
        {
            return GetQueryBuilder(queryInput);
        }

        private IQueryable<TEntity> GetQueryBuilder(QueryInput queryInput = null)
        {
            //根据传入的参数添加过滤条件
            Expression<Func<TEntity, bool>> filter = queryInput != null ?
                ExpressionHelper<TEntity>.CreateExpression(queryInput?.Filter) : null;

            IQueryable<TEntity> query = ForeignKeys.Count > 0 ?
                _repository.GetAllIncluding(ForeignKeys.ToArray()) :
                _repository.GetAll();

            IQueryable<TEntity> queryable = filter != null ? query.Where(filter) : query;
            if (QueryCondition != null) queryable = queryable.Where(QueryCondition);
            return queryable.AsNoTracking();
        }

        /// <summary>
        /// 添加实体方法
        /// </summary>
        /// <param name="input"></param>
        /// <param name="actions"></param>
        /// <returns></returns>
        protected virtual async Task<TListDto> Create(TEditDto input)
        {
            //TODO:新增前的逻辑判断，是否允许新增
            TEntity entity = ObjectMapper.Map<TEntity>(input);

            if (CreateActions != null && CreateActions.Count > 0)
                CreateActions.ForEach(x => x(input, entity));

            if (CreateCheckActions != null && CreateCheckActions.Count > 0)
                CreateCheckActions.ForEach(x => x(entity));

            entity.Id = await _repository.InsertAndGetIdAsync(entity);

            await TriggerChanged(entity);

            return entity.MapTo<TListDto>();
        }

        /// <summary>
        /// 修改实体方法Batch
        /// </summary>
        /// <param name="input"></param>
        /// <param name="actions"></param>
        /// <returns></returns>
        protected virtual async Task<List<TListDto>> BatchUpdate(List<TEditDto> input)
        {
            IQueryable<TEntity> query = ForeignKeys.Count > 0 ?
                _repository.GetAllIncluding(ForeignKeys.ToArray()) :
                _repository.GetAll();

            List<TEntity> entityList = new List<TEntity>();
            List<Task> trigges = new List<Task>();
            List<Task> updateTasks = new List<Task>();
            foreach (TEntity entity in query.Where(x => input.Any(y => x.Id == y.Id)))
            {
                Task task = Task.Run(() =>
                {
                    TEditDto inputEntity = input.Find(x => x.Id == entity.Id);

                    if (UpdateActions != null && UpdateActions.Count > 0)
                        UpdateActions.ForEach(x => x(inputEntity, entity));

                    if (UpdateCheckActions != null && UpdateCheckActions.Count > 0)
                        UpdateCheckActions.ForEach(x => x(entity));

                    trigges.Add(TriggerChanged(entity));
                    entityList.Add(entity);
                });

                updateTasks.Add(task);
            }

            await Task.WhenAll(updateTasks);
            _repository.GetDbContext().UpdateRange(entityList);
            await Task.WhenAll(trigges);

            return entityList.MapTo<List<TListDto>>();
        }

        /// <summary>
        /// 添加实体方法Batch
        /// </summary>
        /// <param name="input"></param>
        /// <param name="actions"></param>
        /// <returns></returns>
        protected virtual async Task<List<TListDto>> BatchCreate(List<TEditDto> input)
        {
            IQueryable<TEntity> query = ForeignKeys.Count > 0 ?
                _repository.GetAllIncluding(ForeignKeys.ToArray()) :
                _repository.GetAll();

            List<TEntity> entityList = new List<TEntity>();
            List<Task> trigges = new List<Task>();
            List<Task> createTasks = new List<Task>();
            foreach (TEditDto inputEntity in input)
            {
                Task task = Task.Run(() =>
                {
                    TEntity entity = inputEntity.MapTo<TEntity>();

                    if (CreateActions != null && CreateActions.Count > 0)
                        CreateActions.ForEach(x => x(inputEntity, entity));

                    if (CreateCheckActions != null && CreateCheckActions.Count > 0)
                        CreateCheckActions.ForEach(x => x(entity));

                    trigges.Add(TriggerChanged(entity));
                    entityList.Add(entity);
                });

                createTasks.Add(task);
            }

            await Task.WhenAll(createTasks);
            _repository.GetDbContext().AddRange(entityList);
            await Task.WhenAll(trigges);
            return entityList.MapTo<List<TListDto>>();
        }

        /// <summary>
        /// 修改实体方法
        /// </summary>
        /// <param name="input"></param>
        /// <param name="actions"></param>
        /// <returns></returns>
        protected virtual async Task<TListDto> Update(TEditDto input)
        {
            IQueryable<TEntity> query = ForeignKeys.Count > 0 ?
                _repository.GetAllIncluding(ForeignKeys.ToArray()) :
                _repository.GetAll();

            TEntity entity = query.FirstOrDefault(x => x.Id == input.Id.Value);

            input.MapTo(entity);

            if (UpdateActions != null && UpdateActions.Count > 0)
                UpdateActions.ForEach(x => x(input, entity));

            if (UpdateCheckActions != null && UpdateCheckActions.Count > 0)
                UpdateCheckActions.ForEach(x => x(entity));

            if (entity != null)
            {
                entity.Id = await _repository.InsertOrUpdateAndGetIdAsync(entity);
                await TriggerChanged(entity);
                return entity.MapTo<TListDto>();
            }

            return null;
        }

        #endregion Protected Methods

        #region Private Methods

        /// <summary>
        /// Excel表格样式、列名处理
        /// </summary>
        /// <typeparam name="TEdit"></typeparam>
        /// <param name="worksheetWrapper"></param>
        private void SetExcelPackageConfig<TEdit>(WorksheetWrapper<TEdit> worksheetWrapper)
        {
            worksheetWrapper.WithConfiguration(
                configuration =>
                {
                    configuration.WithCellConfiguration((x, y) => { x.BorderAround(ExcelBorderStyle.Thin); });
                    configuration.WithColumnConfiguration(x => { x.AutoFit(); });
                    configuration.WithHeaderRowConfiguration(x =>
                    {
                        x.AutoFilter = true;
                        x.SetBackgroundColor(Color.LightBlue);
                    });
                });

            SetExcelColumn(worksheetWrapper);
        }

        protected WorksheetWrapper<TOutEditDto> SetWorksheetExtend<TOutEditDto, TOutEntity, TInputEditDto>
            (IRepository<TOutEntity> repository,
            WorksheetWrapper<TInputEditDto> worksheetWrapper)
            where TOutEntity : class, IEntity
        {
            List<TOutEditDto> list = repository.GetAllList().MapTo<List<TOutEditDto>>();
            WorksheetWrapper<TOutEditDto> worksheet = worksheetWrapper.NextWorksheet(list, L(typeof(TOutEntity).Name));
            SetExcelPackageConfig(worksheet);
            return worksheet;
        }

        /// <summary>
        /// 查询数据并转换为Excel数据格式
        /// </summary>
        /// <param name="queryInput"></param>
        /// <returns></returns>
        private async Task<WorksheetWrapper<TEditDto>> GetExcelInitData(QueryInput queryInput)
        {
            List<TEditDto> listDto = (await PagedDatas(queryInput)).Items.ToList().MapTo<List<TEditDto>>();
            WorksheetWrapper<TEditDto> worksheetWrapper = listDto.ToWorksheet(L(typeof(TEntity).Name));
            SetExcelPackageConfig(worksheetWrapper);
            return worksheetWrapper;
        }

        /// <summary>
        /// 生成Excel表格
        /// </summary>
        /// <param name="worksheetWrapper"></param>
        /// <returns></returns>
        private Task<ExcelPackage> GetExcelData(WorksheetWrapper<TEditDto> worksheetWrapper)
        {
            Task<ExcelPackage> excelPackage;

            excelPackage = Task.Run(() => SetWorksheetExtend(worksheetWrapper));

            return excelPackage;
        }

        /// <summary>
        /// 删除时触发Event
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        private Task TriggerDeleted(TEntity entity)
        {
            return Task.Run(() =>
            {
                EventBus.Trigger(new EntityDeletedEventData<TEntity>(entity));
            });
        }

        /// <summary>
        /// 修改时触发Event
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        private Task TriggerChanged(TEntity entity)
        {
            return Task.Run(() =>
            {
                EventBus.Trigger(new EntityChangedEventData<TEntity>(entity));
            });
        }

        /// <summary>
        /// 处理列名
        /// </summary>
        /// <typeparam name="TEdit"></typeparam>
        /// <param name="worksheetWrapper"></param>
        private void SetExcelColumn<TEdit>(WorksheetWrapper<TEdit> worksheetWrapper)
        {
            //按实体特性ExcelTableColumn(index)排序返回properties
            IEnumerable<PropertyInfo> properties = typeof(TEdit).GetExcelTableColumnAttributesWithProperyInfo()
                .OrderBy(x => x.ColumnAttribute.ColumnIndex)
                .Select(x => x.PropertyInfo);

            foreach (PropertyInfo pi in properties)
            {
                if (pi.Name.Equals("Id") &&
                    pi.DeclaringType == typeof(EntityDto<int?>))
                {
                    continue;
                }

                dynamic Func(TEdit entity)
                {
                    return pi.GetValue(entity).ChangeType(pi.PropertyType);
                }

                Action<ExcelColumn> formatter = null;
                if (pi.PropertyType == typeof(DateTime) || pi.PropertyType == typeof(DateTime?))
                {
                    formatter = (ExcelColumn col) =>
                    {
                        col.Style.Numberformat.Format = "YYYY-MM-DD";
                    };
                }
                worksheetWrapper.WithColumn(Func, L(pi.Name), formatter);
            }
        }

        /// <summary>
        /// 唯一键校验
        /// </summary>
        /// <param name="dataEntity"></param>
        private void CheckUnique(TEntity dataEntity)
        {
            PropertyInfo property = GetUniqueProperty(dataEntity);

            if (property == null) return;

            bool CheckUniqueKey(TEntity entity)
            {
                string value = Convert.ToString(property?.GetValue(entity));
                Expression<Func<TEntity, bool>> expression1 = ExpressionHelper<TEntity>.CreateExpression(property.Name, string.Empty, value, FilterSymbol.Equal);
                return _repository.GetAll().Any(expression1);
            }

            PropertyInfo GetUniqueProperty(TEntity entity)
            {
                return entity.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
                    .FirstOrDefault(pi => pi.GetCustomAttributes(typeof(UniqueKeyAttribute), inherit: true).Length > 0);
            }

            if (!CheckUniqueKey(dataEntity))
            {
                throw new UserFriendlyException(L("UniqueKeyConflict") + ":" + L(property.Name));
            }
        }

        /// <summary>
        /// 导出Excel Sheet页扩展
        /// </summary>
        /// <param name="worksheetWrapper"></param>
        /// <returns></returns>
        private ExcelPackage SetWorksheetExtend(WorksheetWrapper<TEditDto> worksheetWrapper)
        {
            if (ExtendWorksheetWrapper != null) return ExtendWorksheetWrapper(worksheetWrapper);
            return worksheetWrapper.ToExcelPackage();
        }

        #endregion Private Methods
    }
}